/*
 * rls_osi.c - mmWaveLink OS Callback Implementation on Windows
 *
 * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/ 
 * 
 * 
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions 
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the   
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
*/

#include "rls_osi.h"
// #include <process.h>
// #include <windows.h>
#include <stdio.h>
#include <string.h>
#include "WinFuncs.h"


typedef unsigned int UINT32;
typedef void (*P_OS_CALLBACK_FUNCTION)(UINT32);
volatile int           rls_globalLockMutexCounter = 0;
osiLockObj_t* 			rls_pGloblaLockObj = NULL;

//ms
// void Sleep(useconds_t Duration)
// {
// 	usleep(Duration * 1000);
// }

//sleep N ms
int osiSleep(UINT32 Duration)
{	
	Sleep(Duration);
	return OSI_OK;
}

/*******************************************************************************

	SYNC OBJECT

********************************************************************************/
typedef WCHAR *LPCWSTR;
int osiSyncObjCreate(osiSyncObj_t* pSyncObj, char* pName)
{
	if (NULL == pSyncObj)
	{
		return OSI_INVALID_PARAMS;
	}
	//  *pSyncObj = CreateEvent( 
    //     NULL,               // default security attributes
    //     FALSE,              // auto-reset event
    //     FALSE,              // initial state is nonsignaled
    //     (LPCWSTR)pName				// object name
    //     ); 
	UINT32 RetVal = 0;
	RetVal += pthread_mutex_init(&pSyncObj->mutex,NULL);
	
	RetVal += pthread_cond_init(&pSyncObj->cond, NULL);
	
	pSyncObj->signaled = 0;
		
	// memset(pSyncObj, 0, sizeof(osiSyncObj_t));

	// RetVal = sem_init(pSyncObj, 0, 0);

	// pSyncObj->signaled = 0;

    // if (*pSyncObj == NULL) 
    // {
    //     return OSI_OPERATION_FAILED;
    // }
	if (RetVal == 0)
		return OSI_OK;
	else
	{
		return OSI_OPERATION_FAILED;
	}
}


int osiSyncObjDelete(osiSyncObj_t* pSyncObj)
{
	UINT32 RetVal = 0;

	if (NULL == pSyncObj)
	{
		return OSI_INVALID_PARAMS;
	}
	
	// RetVal = CloseHandle(*pSyncObj);
	RetVal += pthread_mutex_destroy(&pSyncObj->mutex);
	RetVal += pthread_cond_destroy(&pSyncObj->cond);
	pSyncObj->signaled = 0;

	// RetVal = sem_destroy(pSyncObj);

	if (RetVal != 0)
	{
		return OSI_OPERATION_FAILED;
	}
	return OSI_OK;
}


int osiSyncObjSignal(osiSyncObj_t* pSyncObj)
{
	
	if (NULL == pSyncObj)
	{
		return OSI_INVALID_PARAMS;
	}
	
	// RetVal = SetEvent(*pSyncObj);
	UINT32 RetVal = 0;

	pthread_mutex_lock(&pSyncObj->mutex);
	pSyncObj->signaled = 1;
	pthread_cond_signal(&pSyncObj->cond);
	// pSyncObj->signaled = 0;
	pthread_mutex_unlock(&pSyncObj->mutex);

	
	// RetVal = sem_post(pSyncObj);

	if (RetVal == 0)
		return OSI_OK;
	else
		return OSI_OPERATION_FAILED;
	// if (FALSE != RetVal)
	// {
	// 	return OSI_OK;
	// }
	// else
	// {
	// 	return OSI_OPERATION_FAILED;
	// }
}


int osiSyncObjWait(osiSyncObj_t* pSyncObj , osiTime_t Timeout)
{
	UINT32 RetVal = 0;
	
	if (NULL == pSyncObj)
	{
		printf("OSI_INVALID_PARAMS\n");
		return OSI_INVALID_PARAMS;
	}

	pthread_mutex_lock(&pSyncObj->mutex);

	// RetVal = WaitForSingleObject(*pSyncObj, Timeout);
	if (pSyncObj->signaled == 0)
	{
		if (Timeout == (osiTime_t)OSI_NO_WAIT)
    	{
		// RetVal = sem_trywait(pSyncObj);
			pthread_mutex_unlock(&pSyncObj->mutex);
			return OSI_TIMEOUT;
    	}
		else if (Timeout == (osiTime_t)OSI_WAIT_FOREVER)
		{
			// RetVal = sem_wait(pSyncObj);
			RetVal = pthread_cond_wait(&pSyncObj->cond, &pSyncObj->mutex);
		}
		else
		{
			// printf("TimeOut: %u\n", Timeout);
			struct timespec ts = {0, 0};
			clock_gettime(CLOCK_REALTIME, &ts);
			long sec = Timeout / 1000;
			long nsec = (Timeout % 1000) * 1000000;
			ts.tv_sec += sec;
			ts.tv_nsec += nsec;
			if (ts.tv_nsec >= 1000000000)
			{
				ts.tv_sec++;
				ts.tv_nsec -= 1000000000;
			}
			// printf("ts:%ld, %ld\n", ts.tv_sec, ts.tv_nsec);
			// RetVal = sem_timedwait(pSyncObj, &ts);
			RetVal = pthread_cond_timedwait(&pSyncObj->cond, &pSyncObj->mutex, &ts);
		}
	}
	
	
	if (WAIT_OBJECT_0 == RetVal)
	{
		pSyncObj->signaled = 0;
		pthread_mutex_unlock(&pSyncObj->mutex);
		return OSI_OK;
	}
	else
	{
		pthread_mutex_unlock(&pSyncObj->mutex);
		printf("OSI_OPERATION_FAILED\n");
		return OSI_TIMEOUT;
	}
}

int osiSyncObjClear(osiSyncObj_t* pSyncObj)
{
	BOOL RetVal;
	
	// if (NULL == pSyncObj || NULL == *pSyncObj)
	if (pSyncObj==NULL)
	{
		return OSI_INVALID_PARAMS;
	}
	
	// RetVal = ResetEvent(*pSyncObj);
	RetVal = pthread_mutex_lock(&pSyncObj->mutex);
	pSyncObj->signaled = 0;
	RetVal = pthread_mutex_unlock(&pSyncObj->mutex);

	if (0 == RetVal)
	{
		return OSI_OK;
	}
	else
	{
		return OSI_OPERATION_FAILED;
	}
}


/*******************************************************************************

		LOCKING OBJECT

********************************************************************************/

int osiLockObjCreate(osiLockObj_t* 		pLockObj, char* pName)
{	
	printf("d\n");
	if (NULL == pLockObj)
	{
		return OSI_INVALID_PARAMS;
	}
		
	// *pLockObj = CreateMutex( 
    //     NULL,              // default security attributes
    //     FALSE,             // initially not owned
    //     NULL);             // unnamed mutex
	if (pthread_mutex_init(pLockObj, NULL) != 0)
	{
		return OSI_OPERATION_FAILED;
	}
	printf("c\n");
	printf("%s\n", pName);
	if (strcmp(pName, "GlobalLockObj") == 0)
	{
		/* save reference to the global lock pointer */
		rls_pGloblaLockObj = pLockObj;

		/* reset the counter */
		rls_globalLockMutexCounter = 0;
	}
	
	return OSI_OK;
}
								 
int osiLockObjDelete(osiLockObj_t* pLockObj)
{
	BOOL RetVal;

	if (NULL == pLockObj)
	{
		return OSI_INVALID_PARAMS;
	}
	
	/* if we are going to delete the "GlobalLock" then wait till all threads
	waiting on this mutex are released */
	if (rls_pGloblaLockObj == pLockObj)
	{	
		//add sleep 1
		while(rls_globalLockMutexCounter)
		{
			osiSleep(1);
		}

		rls_pGloblaLockObj = NULL;

	}
	
	// RetVal = CloseHandle(*pLockObj);
	RetVal = pthread_mutex_destroy(pLockObj);
	
	if (FALSE == RetVal)
	{
		return OSI_OK;
	}
	else
	{
		return OSI_OPERATION_FAILED;
	}
}

int osiLockObjLock(osiLockObj_t* pLockObj , osiTime_t Timeout)
{
	UINT32 RetVal;	

    if (NULL == pLockObj)
	{
		return OSI_INVALID_PARAMS;
	}
	
	/* Increment the global lock counter  */
	if (rls_pGloblaLockObj == pLockObj)
	{
		// InterlockedIncrement(&rls_globalLockMutexCounter);
		__sync_fetch_and_add(&rls_globalLockMutexCounter, 1);
	}

	// RetVal = WaitForSingleObject(*pLockObj,Timeout);
	if (Timeout != OSI_WAIT_FOREVER)
    {
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        long sec = Timeout / 1000;
        long nsec = (Timeout % 1000) * 1000000;
        ts.tv_sec += sec;
        ts.tv_nsec += nsec;
        if (ts.tv_nsec >= 1000000000)
        {
            ts.tv_sec++;
            ts.tv_nsec -= 1000000000;
        }

        RetVal = pthread_mutex_timedlock(pLockObj, &ts);
    }
	else
	{
		RetVal = pthread_mutex_lock(pLockObj);
	}

	/* Decrement the global lock counter  */
	if (rls_pGloblaLockObj == pLockObj)
	{
		// InterlockedDecrement(&rls_globalLockMutexCounter);
		__sync_fetch_and_sub(&rls_globalLockMutexCounter, 1);
	}


	if (WAIT_OBJECT_0 == RetVal)
	{
		return OSI_OK;
	}
	else if (WAIT_TIMEOUT == RetVal) 
	{
		return OSI_TIMEOUT;
	}
    else
    {
        return OSI_OPERATION_FAILED;
    }
}

int osiLockObjUnlock(osiLockObj_t* pLockObj)
{
	BOOL RetVal;
	
	if (NULL == pLockObj)
	{
		return OSI_INVALID_PARAMS;
	}
	
	// RetVal = ReleaseMutex(*pLockObj);
	RetVal = pthread_mutex_unlock(pLockObj);
	
	if (FALSE == RetVal)
	{
		return OSI_OK;
	}
	else
	{
		return OSI_OPERATION_FAILED;
	}
}


/*******************************************************************************

		SPWAN and CONTEXTS

********************************************************************************/



typedef struct spawnThreadEntry
{
	rlsSpawnEntryFunc_t entryFunc;
	const void*         pParam;
}spawnThreadEntry_t;

typedef struct spawnCB
{
    HANDLE  ThreadHdl;
    DWORD   ThreadID;
}spawnCB_t;

spawnCB_t* rls_pSpawnCB=NULL;

#define SPAWN_MESSAGE           (WM_APP + 328) // custom message for thread

int osiSpawn(rlsSpawnEntryFunc_t pEntry , const void* pValue , unsigned int flags)
{
    pEntry(pValue);
    return 0;
}

void* ExecuteEntryFunc(LPVOID lpParam) 
{
    spawnThreadEntry_t* pTh = (spawnThreadEntry_t*)(lpParam);
	pTh->entryFunc(pTh->pParam);
    free(pTh);
}

int osiExecute(rlsSpawnEntryFunc_t pEntry , const void* pValue , unsigned int flags)
{
    pthread_t  ThreadHdl;
    DWORD   ThreadID;
	spawnThreadEntry_t * te = (spawnThreadEntry_t*)malloc(sizeof(spawnThreadEntry_t));
	te->entryFunc = pEntry;
	te->pParam = pValue;

    // ThreadHdl = CreateThread(NULL,0,ExecuteEntryFunc,te,0,&ThreadID);

	pthread_create(&ThreadHdl, NULL, ExecuteEntryFunc, te);
	Sleep(1);
    return 0;
}

unsigned long osiGetTime(void)
{
    // return GetTickCount();
	struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    unsigned long millis = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
    return millis;
}
